package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.common.pojo.PageResult;
import com.leyou.item.pojo.*;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecificationClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.search.repositry.GoodsRepositry;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

//Spu转为Goods，查询一次导入数据
@Service
public class SearchService {
    @Autowired
    private BrandClient brandClient;

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecificationClient specificationClient;

    @Autowired
    private GoodsRepositry goodsRepositry;
    //json转换对象
    private static final ObjectMapper MAPPER = new ObjectMapper();

    public Goods buildGoods(Spu spu) throws IOException {
//        创建goods对象
        Goods goods = new Goods();
        //查询信息
        //查询品牌
        Brand brand = brandClient.queryBrandById(spu.getBrandId());

        //查询分类名称
        List<String> names = categoryClient.queryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));

        //spu-sku集合查询所有sku
        List<Sku> skus = goodsClient.querySkusBySpuId(spu.getId());
        //sku-title,price,image展示字段，id获取依据----封装展示
        //
        List<Long> prices = new ArrayList<>();
        List<Map<String, Object>> skuMapList = new ArrayList<>();
        skus.forEach(
                sku -> {
                    prices.add(sku.getPrice());
                    //封装
                    Map<String, Object> skuMap = new HashMap<>();
                    skuMap.put("title", sku.getTitle());
                    skuMap.put("price", sku.getPrice());
                    skuMap.put("id", sku.getId());
                    //images数据库中是，是否为空，则不显示，否则显示第一张
                    skuMap.put("image", StringUtils.isNotBlank(sku.getImages()) ? StringUtils.split(
                            sku.getImages(), ",")[0] : "");
                    skuMapList.add(skuMap);
                }
        );
        // 查询出所有的搜索规格参数-在spec中通过三级分类id和是否可搜索来确定展示
        List<SpecParam> params = specificationClient.queryParams(null, spu.getCid3(), null, true);

        // 查询spuDetail。获取规格参数值
        SpuDetail spuDetail = goodsClient.querySpuDetailBySpuId(spu.getId());

        //数据库中是json-转为String，展示
        // 获取通用的规格参数-new TR<要转为的类型>(){},抽象类
        Map<Long, Object> genericSpecMap = MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<Long, Object>>() {
        });
        // 获取特殊的规格参数
        Map<Long, List<Object>> specificSpecMap = MAPPER.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<Object>>>() {
        });

        // 定义map接收{规格参数名，规格参数值}
        Map<String, Object> paramMap = new HashMap<>();

        //展示具体参数
        params.forEach(specParam -> {
            if (specParam.getGeneric()) {
                //spu通用参数
                String value = genericSpecMap.get(specParam.getId()).toString();
                if (specParam.getNumeric()) {
                    //数字类型参数
                    value = chooseSegment(value, specParam);//分段展示
                }
                // 把参数名和值放入结果集中
                paramMap.put(specParam.getName(), value);//kv展示类似json前台接收

            } else {
                //sku特定参数
                paramMap.put(specParam.getName(), specificSpecMap.get(specParam.getId()));//kv展示类似json前台接收

            }
        });


//        Spu参数设置给Goods
        goods.setId(spu.getId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setCreateTime(spu.getCreateTime());
        goods.setSubTitle(spu.getSubTitle());
        goods.setAll(spu.getTitle() + " " + StringUtils.join(names, " ") + " " + brand.getName());
        goods.setPrice(prices);
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));//转为s
        goods.setSpecs(paramMap);

//      返回
        return goods;

    }


    /**
     * 选择截取 Value
     *
     * @param value 价值
     * @param p     p
     * @return {@link String}
     */
    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内0-1，1以下，1-2,3-，3以上
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();//正常显示1-2
                }
                break;
            }
        }
        return result;
    }

    /**
     * 搜索--业务功能
     *
     * @param request 请求
     * @return {@link PageResult}<{@link Goods}>
     */
    public SearchResult search(SearchRequest request) {
        String key = request.getKey();
        // 判断是否有搜索条件，如果没有，直接返回null。不允许搜索全部商品
        if (key == null) {
            return null;
        }
//        自定义查询
        //构建查询器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //构建查询
        // 1、对key进行全文检索查询--多个查询条件--交and--基本查询器便于扩展
//        queryBuilder.withQuery(QueryBuilders.matchQuery("all",key).operator(Operator.AND));
        //保存过滤筛选条件
//        QueryBuilder basicQuery = QueryBuilders.matchQuery("all", key).operator(Operator.AND);

        //构建boolean查询
        BoolQueryBuilder basicQuery = buildBooleanQueryBuilder(request);
        queryBuilder.withQuery(basicQuery);


        //结果集过滤 2、通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle,不排除任何null
        queryBuilder.withSourceFilter(
                new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null));


        // 3、分页
        // 准备分页参数--elp分页下标从0开始
        queryBuilder.withPageable(PageRequest.of(request.getPage() - 1, request.getSize()));

        //搜索过滤器--聚合为桶
        String categoryAggName = "categories";
        String brandAggName = "brands";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));


        // 4、查询，获取结果--聚合分页结果集
//        Page<Goods> goodsPage = this.goodsRepositry.search(queryBuilder.build());
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) this.goodsRepositry.search(queryBuilder.build());

        //5.解析为可接受的前台json集合
        // 解析聚合结果集--获取桶
        List<Map<String, Object>> categories = getCategoryAggResult(goodsPage.getAggregation(categoryAggName));
        List<Brand> brands = getBrandAggResult(goodsPage.getAggregation(brandAggName));

        // 5.5-参数处理判断分类聚合的结果集大小，等于1则聚合
        List<Map<String, Object>> specs = null;
        if (categories.size() == 1) {
            specs = getParamAggResult((Long) categories.get(0).get("id"), basicQuery);
        }


        //6.封装 为自定义pageR
        return new SearchResult(goodsPage.getTotalElements(), goodsPage.getTotalPages(), goodsPage.getContent(),
                categories, brands, specs);
    }

    /**
     * 构建bool查询构建器
     *
     * @param request
     * @return
     */
    private BoolQueryBuilder buildBooleanQueryBuilder(SearchRequest request) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 添加基本查询条件-多词条过滤应该是and
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));

        // 添加过滤条件
        if (CollectionUtils.isEmpty(request.getFilter())) {
            return boolQueryBuilder;
        }
        for (Map.Entry<String, Object> entry : request.getFilter().entrySet()) {

            String key = entry.getKey();
            // 如果过滤条件是“品牌”, 过滤的字段名：brandId
            if (StringUtils.equals("品牌", key)) {
                key = "brandId";
            } else if (StringUtils.equals("分类", key)) {
                // 如果是“分类”，过滤字段名：cid3
                key = "cid3";
            } else {
                // 如果是规格参数名，过滤字段名：specs.key.keyword
                key = "specs." + key + ".keyword";
            }
            boolQueryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
        }

        return boolQueryBuilder;
    }

    /**
     * 得到分类分页结果
     *
     * @param aggregation 聚合
     */
    private List<Map<String, Object>> getCategoryAggResult(Aggregation aggregation) {
        //处理聚合结果集，强制转换为filed对应的聚合类
        LongTerms terms = (LongTerms) aggregation;
/*
        // 获取所有的分类id桶
        List<LongTerms.Bucket> buckets = terms.getBuckets();
        // 定义一个品牌集合，搜集所有的品牌对象
        List<Map<String, Object>> categories = new ArrayList<>();

        List<Long> cids = new ArrayList<>();
        //处理桶-查询品牌-获取id集合
        buckets.forEach(
                bucket -> {
                    cids.add(bucket.getKeyAsNumber().longValue());
                }
        );

        //查询-添加到品牌集合
        List<String> names = this.categoryClient.queryNamesByIds(cids);
        //cid，name-组成c
        for (int i = 0; i < cids.size(); i++) {
            Map<String,Object> map = new HashMap<>();
            map.put("id",cids.get(i));
            map.put("name",names.get(i));
            categories.add(map);
        }
           return categories;
*/

        return terms.getBuckets().stream().map(
                bucket -> {
                    Map<String, Object> map = new HashMap<>();
                    Long id = bucket.getKeyAsNumber().longValue();
                    List<String> names = this.categoryClient.queryNamesByIds(Arrays.asList(id));
                    map.put("id", id);
                    map.put("name", names.get(0));
                    return map;
                }
        ).collect(Collectors.toList());


    }

    /**
     * 得到品牌聚合分页结果
     *
     * @param aggregation 聚合
     */
    private List<Brand> getBrandAggResult(Aggregation aggregation) {
        //处理聚合结果集，强制转换为filed对应的聚合类
        LongTerms terms = (LongTerms) aggregation;
        //获取id桶
        List<LongTerms.Bucket> buckets = terms.getBuckets();

        // 定义一个品牌集合，搜集所有的品牌对象
        List<Brand> brands = new ArrayList<>();
        //解析id查询品牌
        buckets.forEach(bucket -> {
            Brand brand = this.brandClient.queryBrandById(bucket.getKeyAsNumber().longValue());
            brands.add(brand);
        });

        return brands;
    }

    /**
     * 聚合出参数过滤条件
     *
     * @param id         id
     * @param basicQuery 基本查询
     */
    private List<Map<String, Object>> getParamAggResult(Long id, QueryBuilder basicQuery) {
        //查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 基于基本的查询条件，聚合规格参数--在之前查询的基础上
        queryBuilder.withQuery(basicQuery);

        // 查询要聚合的规格参数
        List<SpecParam> params = this.specificationClient.queryParams(null, id, null, true);

        //添加聚合
        params.forEach(specParam -> {
            //品牌名称聚合不分词--terms
            //因为规格参数保存时不做分词
            queryBuilder.addAggregation(AggregationBuilders.terms(specParam.getName()).field("specs." + specParam.getName() + ".keyword"));
        });
        // 只需要聚合结果集，不需要查询结果集
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));


        //执行查询
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) this.goodsRepositry.search(queryBuilder.build());

        // 定义一个集合，收集聚合结果集
        List<Map<String, Object>> paramMapList = new ArrayList<>();

        //处理桶
        //聚合桶转为集合kv,name，value
        Map<String, Aggregation> aggregationMap = goodsPage.getAggregations().asMap();
        for (Map.Entry<String, Aggregation> entry : aggregationMap.entrySet()) {
            Map<String, Object> map = new HashMap<>();
            // 放入规格参数名--前台约定好，接收此参数
            map.put("k", entry.getKey());
            // 收集规格参数值
            List<Object> options = new ArrayList<>();
            // 解析每个聚合--StringTerms--参数值都是keyword，StringTerm接收
            StringTerms terms = (StringTerms) entry.getValue();

            // 遍历每个聚合中桶，把桶中key放入收集规格参数的集合中
            terms.getBuckets().forEach(bucket -> {
                options.add(bucket.getKeyAsString());//具体值
            });
            map.put("options", options);
            paramMapList.add(map);
        }

        return paramMapList;
    }

    /**
     * 保存
     *
     * @param spuId spu id
     */
    public void save(Long spuId) throws IOException {
        Spu spu = this.goodsClient.querySpuById(spuId);
        Goods goods = buildGoods(spu);
        this.goodsRepositry.save(goods);
    }

    /**
     * 删除
     *
     * @param spuId spu id
     */
    public void delete(Long spuId) {
        this.goodsRepositry.deleteById(spuId);
    }
}
